#include "Level.h"
#include "Camera.h"
#include <exception>
#include <physfs.h>
#include "rapidxml.hpp"
#include "Collision.h"
#include "Entity.h"
#include "Collideable.h"
#include "ExtraMath.h"
#include <fstream>
#include "AnimatedTile.h"
#include "SlopeTile.h"
#include "Resources.h"

//void initCollisionParser(std::map<std::string, unsigned int>&);
Level::Level(const std::string& filepath, int tileSize)
{
	_filepath = "levels/" + filepath;
	_tileSize = tileSize;
	_gravity = 9.8f;
}

Level::~Level(void){}

void Level::setDimension()
{
	_map.resize(_h);
	for (unsigned int i = 0; i < _map.size(); i++)
		_map[i].resize(_w, nullptr);
	Camera::getSingleton()->setMapSize(sf::Vector2f(static_cast<float>(_w * _tileSize), static_cast<float>(_h * _tileSize)));
}

void Level::update()
{
	if (!_animatedTiles.empty())
	{
		for (auto it = _animatedTiles.begin(); it != _animatedTiles.end(); it++)
			(*it)->update();
	}
}

void Level::draw(RenderManager* renderManager)
{
	sf::IntRect tileBounds = Camera::getSingleton()->getTilebounds(_tileSize);

	unsigned int y = tileBounds.top;
	unsigned int yMax = tileBounds.height + y;
	if (yMax > _map.size())
		yMax = _map.size();
	unsigned int x = tileBounds.left;
	unsigned int xMax = tileBounds.width + x;
	if (xMax > _map.front().size())
		xMax = _map.front().size();

	for (; y < yMax; y++)
	{
		for (; x < xMax; x++)
		{
			if (_map[y][x] != nullptr)
				_map[y][x]->draw(renderManager);
		}
		x = 0;
	}
}

Tile* Level::getTile(unsigned int x, unsigned int y)
{
	return _map[x][y];
}

void Level::loadLevel()
{
	std::string xmlContent;
	if (Resources::getSingletonPtr()->getResourceMode() == RESOURCE_LOAD_FROM_ARCHIVE)
	{
		if (!PHYSFS_exists(_filepath.c_str()))
		{
			throw "Cannot find " + _filepath;
			abort();
		}

		PHYSFS_file* file = PHYSFS_openRead(_filepath.c_str());
		PHYSFS_sint64 file_size = PHYSFS_fileLength(file);
		char* buffer = new char[static_cast<PHYSFS_sint32>(file_size)];
		PHYSFS_read(file, buffer, 1, static_cast<PHYSFS_uint32>(file_size));
		buffer[file_size] = '\0';
		xmlContent = buffer;
		PHYSFS_close(file);
		PHYSFS_delete(buffer);
	}
	else if (Resources::getSingletonPtr()->getResourceMode() == RESOURCE_LOAD_FROM_FILEPATH)
	{
		std::ifstream in(_filepath);
		if (!in)
		{
			throw "Cannot find " + _filepath;
			abort();
		}
		std::string line;
		while (std::getline(in, line))
			xmlContent += line;
		in.close();
	}
	
	std::vector<char> _xmlBuffer = std::vector<char>(xmlContent.begin(), xmlContent.end());
	_xmlBuffer.push_back('\0');
	rapidxml::xml_document<> doc;
	doc.parse<rapidxml::parse_no_data_nodes>(&_xmlBuffer[0]);
	rapidxml::xml_node<>* root = doc.first_node();
	
	_w = atoi(root->first_attribute("width")->value());
	_h = atoi(root->first_attribute("height")->value());
	_id = root->first_attribute("id")->value();
	setDimension();
	rapidxml::xml_node<>* tileset = root->first_node("tileset");
	std::string path = tileset->first_attribute("path")->value();
	loadTileset("tilesets/" + path);

	rapidxml::xml_node<>* tile = root->first_node("tile");

	//std::map<std::string, unsigned int> cp;
	//initCollisionParser(cp);

	while (tile)
	{
		float x = static_cast<float>(atof(tile->first_attribute("x")->value()));
		float y = static_cast<float>(atof(tile->first_attribute("y")->value()));
		int baseid = atoi(tile->first_attribute("baseid")->value());
		std::string collision = tile->first_attribute("collision")->value();
		short flag;
		flag = flag & ~TILE_TYPE_SLOPE;
		flag = flag & ~TILE_TYPE_COLLIDEABLE;
		bool walkable = (collision == "true") ? true : false;
		if (walkable)
			flag |= TILE_TYPE_COLLIDEABLE;
		int xSize = _tiles[baseid].getSize().x;
		if (xSize > _tileSize)
		{
			flag |= TILE_TYPE_ANIMATED;
			AnimatedTile* newTile = new AnimatedTile(_tileSize, sf::Vector2f(x * static_cast<float>(_tileSize), y * static_cast<float>(_tileSize)), _tiles[baseid], flag);
			_map[static_cast<unsigned int>(y)][static_cast<unsigned int>(x)] = newTile;
			_animatedTiles.push_back(newTile);
		}
		else if (collision != "slope")
		{
			Tile* newTile = new Tile(_tileSize, sf::Vector2f(x * static_cast<float>(_tileSize), y * static_cast<float>(_tileSize)), _tiles[baseid], flag);
			_map[static_cast<unsigned int>(y)][static_cast<unsigned int>(x)] = newTile;
		}
		else if (collision == "slope")
		{
			flag |= TILE_TYPE_COLLIDEABLE;
			flag |= TILE_TYPE_SLOPE;
			SlopeTile* newTile = new SlopeTile(_tileSize, sf::Vector2f(x * static_cast<float>(_tileSize), y * static_cast<float>(_tileSize)), _tiles[baseid], flag);
			_map[static_cast<unsigned int>(y)][static_cast<unsigned int>(x)] = newTile;
		}

		tile = tile->next_sibling("tile");
	}
	
	
	//CollisionFactory::clean();
}

void Level::loadTileset(const std::string& path)
{
	std::string xmlContent;
	if (Resources::getSingletonPtr()->getResourceMode() == RESOURCE_LOAD_FROM_ARCHIVE)
	{
		if (!PHYSFS_exists(path.c_str()))
		{
			throw "Cannot find " + path;
			abort();
		}

		PHYSFS_file* file = PHYSFS_openRead(path.c_str());
		PHYSFS_sint64 file_size = PHYSFS_fileLength(file);
		char* buffer = new char[static_cast<PHYSFS_sint32>(file_size)];
		PHYSFS_read(file, buffer, 1, static_cast<PHYSFS_uint32>(file_size));
		buffer[file_size] = '\0';
		xmlContent = buffer;
		PHYSFS_close(file);
		PHYSFS_delete(buffer);
	}
	else if (Resources::getSingletonPtr()->getResourceMode() == RESOURCE_LOAD_FROM_FILEPATH)
	{
		std::ifstream in(path);
		if (!in)
		{
			throw "Cannot find " + path;
			abort();
		}
		std::string line;
		while (std::getline(in, line))
			xmlContent += line;
		in.close();
	}

	
	std::vector<char> _xmlBuffer = std::vector<char>(xmlContent.begin(), xmlContent.end());
	_xmlBuffer.push_back('\0');
	rapidxml::xml_document<> doc;
	doc.parse<rapidxml::parse_no_data_nodes>(&_xmlBuffer[0]);
	rapidxml::xml_node<>* root = doc.first_node();
	rapidxml::xml_node<>* imageFile = root->first_node("imagefile");
	std::string imagePath = imageFile->first_attribute("path")->value();
	sf::Texture* tileset = TextureManager::getSingletonPtr()->loadTexture(imagePath, _id);
	sf::Image tempImage = tileset->copyToImage();
	rapidxml::xml_node<>* tile = imageFile->first_node("tile");
	while (tile)
	{
		int x = atoi(tile->first_attribute("x")->value());
		int y = atoi(tile->first_attribute("y")->value());
		int frames = atoi(tile->first_attribute("frames")->value());
		int id = atoi(tile->first_attribute("id")->value());

		sf::Texture tileTexture;
		tileTexture.create(_tileSize, _tileSize);
		tileTexture.loadFromImage(tempImage, sf::IntRect(x * _tileSize, y * _tileSize, _tileSize * frames, _tileSize));
		_tiles.push_back(tileTexture);
		tile = tile->next_sibling();
	}
	TextureManager::getSingletonPtr()->unloadTexture(_id);
}

void Level::unloadLevel()
{
	for (auto it = _map.begin(); it != _map.end(); it++)
	{
		for (auto it2 = (*it).begin(); it2 != (*it).end(); it2++)
		{
			delete *it2;
			*it2 = nullptr;
		}
		(*it).clear();
	}
	_map.clear();
	_tiles.clear();
	_animatedTiles.clear();
}

void Level::checkCollision(Entity* entity)
{
	//isOnGround(entity);

	//if ((entity->flags() & GRAVITY_AFFECTED) == GRAVITY_AFFECTED)
		//applyGravity(entity);
	Collideable* collision = dynamic_cast<Collideable*>(entity);
	sf::FloatRect box = collision->collisionBox(VELOCITY_AFTER_MOVEMENT);
	const float collisionBoundary = 4.0f;
	int x = static_cast<int>(box.left - ((box.width * collisionBoundary) * 0.5f)) / _tileSize;
	int y = static_cast<int>(box.top - ((box.height * collisionBoundary) * 0.5f)) / _tileSize;
	int w = static_cast<int>((box.width * collisionBoundary)) / _tileSize;
	int h = static_cast<int>((box.height * collisionBoundary)) / _tileSize;

	bool checkX = true;
	bool checkY = true;

	if (x < 0) x = 0;
	if (y < 0) y = 0;
	unsigned int xMax = x + w;
	unsigned int yMax = y + h;
	if (xMax > _map.front().size()) xMax = _map.front().size();
	if (yMax > _map.size()) yMax = _map.size();

	for (; static_cast<unsigned int>(y) < yMax; y++)
	{
		for (; static_cast<unsigned int>(x) < xMax; x++)
		{
			if (_map[y][x] != nullptr)
			{
				if ((_map[y][x]->getType() & TILE_TYPE_COLLIDEABLE) == TILE_TYPE_COLLIDEABLE)
				{
					if (_map[y][x]->collisionBox().intersects(collision->collisionBox(VELOCITY_AFTER_MOVEMENT_X)) && checkX)
					{
						if ((_map[y][x]->getType() & TILE_TYPE_SLOPE) != TILE_TYPE_SLOPE)
						{
							if (entity->_velocity.x > 0.0f)
							{
								entity->_velocity.x = _map[y][x]->collisionBox().left - (collision->collisionBox().left + collision->collisionBox().width);
								entity->setFlag(COLLISION_HIT_RIGHT);
							}
							else if (entity->_velocity.x < 0.0f)
							{
								entity->_velocity.x = (_map[y][x]->collisionBox().left + _map[y][x]->collisionBox().width) - collision->collisionBox().left;
								entity->setFlag(COLLISION_HIT_LEFT);
							}
							checkX = false;
						}
						else if (entity->_velocity.x != 0.0f)
						{
							if (Collision::slopeCollision(entity, collision, _map[y][x], collision->collisionBox(VELOCITY_AFTER_MOVEMENT_X)))
							{
								checkX = false;
							}
						}
					}
				
					if (_map[y][x]->collisionBox().intersects(collision->collisionBox(VELOCITY_AFTER_MOVEMENT_Y)) && checkY)
					{
						if ((_map[y][x]->getType() & TILE_TYPE_SLOPE) != TILE_TYPE_SLOPE)
						{
							if (entity->_velocity.y > 0.0f)
							{
								entity->_position.y = _map[y][x]->collisionBox().top - collision->collisionBox().height * 0.5f;
								if (!entity->_onGround)
								{
									entity->_onGround = true;
									entity->setFlag(JUST_LANDED);
								}
									entity->_velocity.y = 0.0f;
							}
							else if (entity->_velocity.y < 0.0f)
							{
								entity->_velocity.y = (_map[y][x]->collisionBox().top + _map[y][x]->collisionBox().height) - collision->collisionBox().top;
							}
							checkY = false;
						}
						else
						{
							if (Collision::slopeCollision(entity, collision, _map[y][x], collision->collisionBox(VELOCITY_AFTER_MOVEMENT_Y)))
							{
								if (entity->_velocity.y > 0.0f)
								{
									if (!entity->_onGround)
									{
										entity->setFlag(JUST_LANDED);
										entity->_onGround = true;
									}
									entity->_velocity.y = 0.0f;
								}
							checkY = false;
							}
						}
					}
				}
			}
		}
		x = 0;
	}
}

void Level::isOnGround(Entity* entity)
{
	bool isOnGround = false;
	Collideable* collision = dynamic_cast<Collideable*>(entity);
	if (entity->_onGround)
	{
		sf::FloatRect sensor = collision->collisionBox(FLOOR_SENSOR);
		float collisionBoundary = 8.0f;
		int x = static_cast<int>(sensor.left - ((sensor.width * collisionBoundary) * 0.5f)) / _tileSize;
		int y = (static_cast<int>(sensor.top) - _tileSize) / _tileSize;
		int w = static_cast<int>((sensor.width * collisionBoundary)) / _tileSize;
		int h = (_tileSize * ((unsigned short)collisionBoundary * 2)) / _tileSize;

		if (x < 0) x = 0;
		if (y < 0) y = 0;
		unsigned int xMax = x + w;
		unsigned int yMax = y + h;
		if (xMax > _map.front().size()) xMax = _map.front().size();
		if (yMax > _map.size()) yMax = _map.size();
	
	
		for (; static_cast<unsigned int>(y) < yMax; y++)
		{
			for (; static_cast<unsigned int>(x) < xMax; x++)
			{
				if ((_map[y][x]->getType() & TILE_TYPE_COLLIDEABLE) == TILE_TYPE_COLLIDEABLE)
				{
					if (_map[y][x]->collisionBox().intersects(sensor))
					{
						if ((_map[y][x]->getType() & TILE_TYPE_SLOPE) == TILE_TYPE_SLOPE)
						{
							//if (Collision::pixelPerfect(_map[y][x], sensor, _tileSize))
								isOnGround = true;
						}
						else
							isOnGround = true;
					}
				}
			}
			x = 0;
			if (isOnGround)
				break;
		}
	}
	
	if (!isOnGround)
		entity->_onGround = false;
	else
		entity->_onGround = true;
	//return isOnGround;
}

void Level::applyGravity(Entity* entity)
{
	
	if (entity->_onGround)
		entity->_velocity.y = 4.0f;
	else
		entity->_velocity.y += (_gravity + entity->mass()) * GameTime::getSingleton()->deltaTime();
	// apply additional gravity for slopes
	// remove/tweak this at a later stage?
}

void Level::clearFlags(Entity* entity)
{
	if ((entity->flags() & COLLISION_HIT_LEFT) == COLLISION_HIT_LEFT)
		entity->removeFlag(COLLISION_HIT_LEFT);

	if ((entity->flags() & COLLISION_HIT_RIGHT) == COLLISION_HIT_RIGHT)
		entity->removeFlag(COLLISION_HIT_RIGHT);
}

/*void initCollisionParser(std::map<std::string, unsigned int>& cp)
{
	cp.insert(std::make_pair(std::string("hor"), TILE_TYPE_HORIZONTAL));
	cp.insert(std::make_pair(std::string("hor_left"), TILE_TYPE_HORIZONTAL_LEFT));
	cp.insert(std::make_pair(std::string("hor_right"), TILE_TYPE_HORIZONTAL_RIGHT));
	cp.insert(std::make_pair(std::string("ver"), TILE_TYPE_VERTICAL));
	cp.insert(std::make_pair(std::string("ver_top"), TILE_TYPE_VERTICAL_TOP));
	cp.insert(std::make_pair(std::string("ver_bot"), TILE_TYPE_VERTICAL_BOT));
	cp.insert(std::make_pair(std::string("rec"), TILE_TYPE_RECTANGULAR));
	cp.insert(std::make_pair(std::string("slope_left"), TILE_TYPE_SLOPE_LEFT));
	cp.insert(std::make_pair(std::string("slope_right"), TILE_TYPE_SLOPE_RIGHT));
	cp.insert(std::make_pair(std::string("tl"), TILE_TYPE_UPPER_LEFT));
	cp.insert(std::make_pair(std::string("tr"), TILE_TYPE_UPPER_RIGHT));
	cp.insert(std::make_pair(std::string("bl"), TILE_TYPE_BOTTOM_LEFT));
	cp.insert(std::make_pair(std::string("br"), TILE_TYPE_BOTTOM_RIGHT));
	cp.insert(std::make_pair(std::string("none"), TILE_TYPE_NONE));
}*/